package server;

import config.ApplicationConfig;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import util.GetRSS;
import util.JudgeSubject;
import util.PlayBloodAudio;
import util.ReadConfig;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * User: tum0r
 * Package Name: server
 * Time: 2019-04-22 Monday 15:33:42
 */
public class RecordServer extends BaseServer implements Runnable {

    private LinkedList<String> dangerCode;

    private long result = Long.MAX_VALUE;

    private PlayBloodAudio playBloodAudio = new PlayBloodAudio();

    private void getMinWaitTime() {
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select * from record where result=? or result=?", new Object[]{"Compile...", "Running..."});
        Iterator<LinkedHashMap<String, Object>> iterator = temp.iterator();
        LinkedHashMap<String, Object> item;
        while (iterator.hasNext()) {
            item = iterator.next();
            String subjectID = item.get("subject_id") == null ? "" : item.get("subject_id").toString();
            String language = item.get("language") == null ? "" : item.get("language").toString();

            LinkedList<LinkedHashMap<String, Object>> subjectTemp = sql.query("select * from subject where uuid=?", new Object[]{subjectID});
            if (subjectTemp.size() > 0) {
                LinkedHashMap<String, Object> subjectItem = subjectTemp.get(0);
                JSONArray answerLimit = JSONArray.fromObject(subjectItem.get("answer_limit").toString());
                JSONObject limit = JSONArray.fromObject(subjectItem.get("answer_limit").toString()).getJSONObject(0);
                for (int i = 0; i < answerLimit.size(); i++) {
                    limit = JSONArray.fromObject(subjectItem.get("answer_limit").toString()).getJSONObject(i);
                    if (limit.getString("language").equals(language)) {
                        break;
                    }
                }
                result = Math.min(result, (limit.getLong("timeLimit") * 2 + 1000) / 1000);
            }
        }
    }

    public long getWaitTime() {
        getMinWaitTime();
        return result;
    }

    public boolean recordSubject(String subjectID, String user, String code, String language) {
        boolean result = false;
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select uuid from user where username=? and state=?", new Object[]{user, 1});
        if (temp.size() > 0) {
            String userID = temp.get(0).get("uuid").toString();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            String recordID = UUID.randomUUID().toString();
            result = sql.update("insert into record values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{recordID, subjectID, userID, code, language, "Compile...", "-", "-", simpleDateFormat.format(new Date()), 0}) == 1;
        }
        return result;
    }

    public LinkedList<String> getUserAccepted(String user) {
        LinkedList<String> result = new LinkedList<>();
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select uuid from user where username=? and state=?", new Object[]{user, 1});
        if (temp.size() > 0) {
            String userID = temp.get(0).get("uuid").toString();
            temp = sql.query("select subject_id from record where user_id=? and result=?", new Object[]{userID, "Accepted"});
            Iterator<LinkedHashMap<String, Object>> iterator = temp.iterator();
            String uuid;
            while (iterator.hasNext()) {
                uuid = (String) iterator.next().get("subject_id");
                if (!result.contains(uuid)) {
                    result.add(uuid);
                }
            }
        }
        return result;
    }

    public LinkedList<String> getBlood(String subject_id) {
        LinkedList<String> result = new LinkedList<>();
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select user_id from record where subject_id=? and result=? order by submit_time", new Object[]{subject_id, "Accepted"});
        int count = 0;
        LinkedList<LinkedHashMap<String, Object>> userTemp;
        String username;
        for (LinkedHashMap<String, Object> stringObjectLinkedHashMap : temp) {
            userTemp = sql.query("select username from user where uuid=? and type=? and state=?", new Object[]{stringObjectLinkedHashMap.get("user_id"), 0, 1});
            if (userTemp.size() == 1) {
                username = (String) userTemp.get(0).get("username");
                if (!result.contains(username)) {
                    result.addLast(username);
                    if (++count == 3) {
                        break;
                    }
                }
            }
        }
        for (; count < 3; count++) {
            result.addLast("");
        }
        return result;
    }

    public String judgeBlood(int index) {
        StringBuilder result = new StringBuilder();

        switch (index) {
            case 0:
                result.append(ApplicationConfig.getBloodsAudio("First-Blood"));
                break;
            case 1:
                result.append(ApplicationConfig.getBloodsAudio("Second-Blood"));
                break;
            case 2:
                result.append(ApplicationConfig.getBloodsAudio("Third-Blood"));
                break;
        }
        return result.toString();
    }

    public LinkedHashMap<String, Integer> getAcceptedSubmitted(String subjectID) {
        LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
        int acceptedCount = 0;
        int submittedCount = 0;
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select user_id from record where result=? and subject_id=?", new Object[]{"Accepted", subjectID});
        for (LinkedHashMap<String, Object> item : temp) {
            if (sql.query("select uuid from user where uuid=? and type=? and state=?", new Object[]{item.get("user_id"), 0, 1}).size() == 1) {
                acceptedCount++;
            }
        }

        temp = sql.query("select user_id from record where subject_id=?", new Object[]{subjectID});
        for (LinkedHashMap<String, Object> item : temp) {
            if (sql.query("select uuid from user where uuid=? and type=? and state=?", new Object[]{item.get("user_id"), 0, 1}).size() == 1) {
                submittedCount++;
            }
        }

        result.put("acceptedCount", acceptedCount);
        result.put("submittedCount", submittedCount);
        return result;
    }

    public LinkedList<LinkedHashMap<String, Object>> getAcceptedRecord() {
        LinkedList<LinkedHashMap<String, Object>> result = new LinkedList<>();
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select user_id,username,subject_id,title,language,time,memory,submit_time from record,user,subject where result=? and user_id=user.uuid and subject_id=subject.uuid and user.type=? order by submit_time;", new Object[]{"Accepted", 0});
        LinkedHashMap<String, Object> tempItem;
        LinkedHashMap<String, Object> resultItem;
        boolean flag;
        int i;
        int j;
        for (i = 0; i < temp.size(); i++) {
            flag = true;
            tempItem = temp.get(i);
            for (j = 0; j < result.size(); j++) {
                resultItem = result.get(j);
                if (String.valueOf(resultItem.get("subject_id")).equals(String.valueOf(tempItem.get("subject_id"))) && String.valueOf(resultItem.get("user_id")).equals(String.valueOf(tempItem.get("user_id")))) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                result.addLast(tempItem);
            }
        }
        return result;
    }

    public boolean hasCompile(String user) {
        boolean result = false;
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select uuid from user where username=? and state=?", new Object[]{user, 1});
        if (temp.size() > 0) {
            String userID = temp.get(0).get("uuid").toString();
            result = sql.query("select uuid from record where user_id=? and (result=? or result=?)", new Object[]{userID, "Compile...", "Running..."}).size() > 0;
        }
        return result;
    }

    public void deleteRecordByUser(String user) {
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select uuid from user where username=?", new Object[]{user});
        if (temp.size() > 0) {
            String userID = temp.get(0).get("uuid").toString();
            sql.query("delete from record where user_id=?", new Object[]{userID});
        }
    }

    public void clearRecord(String mode) {
        LinkedList<LinkedHashMap<String, Object>> temp;
        switch (mode) {
            case "1":
                temp = sql.query("select uuid from user where type=?", new Object[]{1});
                break;
            case "2":
                temp = sql.query("select uuid from user", null);
                break;
            default:
                temp = sql.query("select uuid from user where type=?", new Object[]{0});
                break;
        }
        for (LinkedHashMap<String, Object> stringObjectLinkedHashMap : temp) {
            sql.update("delete from record where user_id=?", new Object[]{stringObjectLinkedHashMap.get("uuid")});
        }
    }

    public void punished(String userID, String reason) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        sql.update("insert into record values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{UUID.randomUUID().toString(), UUID.randomUUID().toString(), userID, reason, "加罚时", "Punished", "-", "-", simpleDateFormat.format(new Date()), 1});
    }

    public void removePunished(String userID) {
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select * from record where result=? and user_id=? order by submit_time desc", new Object[]{"Punished", userID});
        if (temp.size() > 0) {
            sql.update("delete from record where uuid=?", new Object[]{temp.get(0).get("uuid")});
        }
    }

    public void recompile(String subject_id) {
        sql.update("update record set result='Compile...',state=0 where subject_id=?", new Object[]{subject_id});
    }

    @Override
    public void run() {
        LinkedList<LinkedHashMap<String, Object>> temp = sql.query("select * from record where result=? and state=?", new Object[]{"Compile...", 0});
        Iterator<LinkedHashMap<String, Object>> iterator = temp.iterator();
        LinkedHashMap<String, Object> item;
        boolean flag;
        JudgeSubject judgeSubject = null;
        while (iterator.hasNext()) {
            item = iterator.next();
            sql.update("update record set state=1 where uuid=?", new Object[]{item.get("uuid")});
            GetRSS getRSS = new GetRSS();
            flag = false;
            String recordID = item.get("uuid") == null ? "" : item.get("uuid").toString();
            String subjectID = item.get("subject_id") == null ? "" : item.get("subject_id").toString();
            String userID = item.get("user_id") == null ? "" : item.get("user_id").toString();
            String code = item.get("code") == null ? "" : item.get("code").toString();
            String language_judge = item.get("language") == null ? "" : item.get("language").toString();
            String language = (language_judge.equals("C") || language_judge.equals("CPP")) ? "C/CPP" : language_judge;

            LinkedList<String> oldBlood = getBlood(subjectID);

            LinkedList<LinkedHashMap<String, Object>> subjectTemp = sql.query("select * from subject where uuid=?", new Object[]{subjectID});
            if (subjectTemp.size() > 0) {
                LinkedHashMap<String, Object> subjectItem = subjectTemp.get(0);
                String languageType = language;
                JSONArray answerLimit = JSONArray.fromObject(subjectItem.get("answer_limit").toString());
                JSONObject limit = JSONArray.fromObject(subjectItem.get("answer_limit").toString()).getJSONObject(0);
                for (int i = 0; i < answerLimit.size(); i++) {
                    limit = JSONArray.fromObject(subjectItem.get("answer_limit").toString()).getJSONObject(i);
                    if (limit.getString("language").equals(languageType)) {
                        break;
                    }
                }

                String testCase = subjectItem.get("test_case").toString();
                String testCaseResult = subjectItem.get("test_case_result").toString();
                long timeLimit = limit.getLong("timeLimit");
                long memoryLimit = limit.getLong("memoryLimit");

                judgeSubject = new JudgeSubject(recordID, userID, subjectID, language, timeLimit, memoryLimit, testCase, testCaseResult, subjectItem.get("format").toString(), getRSS);

                String codeLowerCase = code.toLowerCase();
                boolean notDanger = true;

                dangerCode = ApplicationConfig.getDangerCode(language);
                for (int i = 0; i < dangerCode.size(); i++) {
                    if (codeLowerCase.contains(dangerCode.get(i))) {
                        judgeSubject.setResult("Danger Code");
                        notDanger = false;
                        break;
                    }
                }

                if (notDanger) {
                    try {
                        String path = "judge" + File.separator + userID + File.separator + subjectID + File.separator + recordID + File.separator;
                        File folder = new File(path);
                        if (!folder.exists()) {
                            folder.mkdirs();
                        }

                        File file;

                        if (language_judge.equals("Java")) {
                            file = new File(path + "Main.java");
                        } else if (language_judge.equals("C")) {
                            file = new File(path + "Main.c");
                        } else if (language_judge.equals("CPP")) {
                            file = new File(path + "Main.cpp");
                        } else {
                            file = new File(path + "Main.py");
                        }

                        FileOutputStream fileOutputStream = new FileOutputStream(file);
                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
                        BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
                        bufferedWriter.write(code);
                        bufferedWriter.close();
                        outputStreamWriter.close();
                        fileOutputStream.close();

                        Runtime runtime = Runtime.getRuntime();
                        String[] compilation;
                        String codePath = folder.getAbsolutePath() + File.separator;
                        if (language_judge.equals("Java")) {
                            compilation = new String[2];
                            compilation[0] = "javac";
                            compilation[1] = codePath + "Main.java";
                        } else if (language_judge.equals("C")) {
                            compilation = new String[4];
                            compilation[0] = "gcc";
                            compilation[1] = codePath + "Main.c";
                            compilation[2] = "-o";
                            compilation[3] = codePath + "Main.out";
                        } else if (language_judge.equals("CPP")) {
                            compilation = new String[4];
                            compilation[0] = "g++";
                            compilation[1] = codePath + "Main.cpp";
                            compilation[2] = "-o";
                            compilation[3] = codePath + "Main.out";
                        } else {
                            compilation = new String[2];
                            compilation[0] = "ls";
                            compilation[1] = path + "Main.py";
                        }
                        Process process = runtime.exec(compilation);
                        int state = process.waitFor();

                        if (state == 0) {
                            sql.update("update record set result=? where uuid=?", new Object[]{"Running...", recordID});
                            Thread thread = new Thread(judgeSubject);
                            judgeSubject.setThread(thread);
                            thread.start();
                            try {
                                Thread.sleep(timeLimit * 2 + 1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (thread.isAlive()) {
                                thread.stop();
                                judgeSubject.setTime(timeLimit * 2);
                            }
                        } else {
                            judgeSubject.setResult("Compilation Error");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                flag = true;
            }
            String result = judgeSubject.getResult();
            sql.update("update record set result=?,time=?,memory=? where uuid=?", new Object[]{flag ? result : "Wrong Answer", judgeSubject.getTime(), getRSS.getRss(), recordID});
            if (result != null && result.equals("Accepted")) {
                RecordServer recordServer = new RecordServer();
                recordServer.open();
                LinkedList<String> newBlood = recordServer.getBlood(subjectID);
                recordServer.close();
                boolean play = false;
                int index = 0;
                for (; index < oldBlood.size(); index++) {
                    if (!oldBlood.get(index).equals(newBlood.get(index))) {
                        play = true;
                        break;
                    }
                }
                if (play) {
                    playBloodAudio.setAudioFile(judgeBlood(index));
                    new Thread(playBloodAudio).start();
                }
            }
            close();
        }
    }
}
